/*
    Revolution Engine
    Copyright (C) 2009 Felipe Apablaza

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/


/**
 *
 *  File: Vector4.hpp
 *  Desc: Four-Dimensional vector class template.
 *
 */

#ifndef __REVENG_MATH_VECTOR4_HPP__
#define __REVENG_MATH_VECTOR4_HPP__

#include <cmath>
#include "Base.hpp"

namespace RevEng
{
    namespace Math
    {        
		template<typename T>
		struct Vector4
		{
            T X;
            T Y;
            T Z;
            T W;
					
			//Constructor #1
			Vector4<T>();

			//Constructor #2
			Vector4<T>(const T *Array);

			//Constructor #3
			Vector4<T>(T x, T y, T z, T w);

			//Constructor copia
			Vector4<T>(const Vector4<T> &v);

			//Suma dos vectores
			Vector4<T> operator+ (const Vector4<T> &Other) const;

			//Suma dos vectores
			Vector4<T> &operator+= (const Vector4<T> &Other);

			//Resta dos vectores
			Vector4<T> operator- (const Vector4<T> &Other) const;

			//Resta dos vectores
			Vector4<T> &operator-= (const Vector4<T> &Other);

			//Escala un vector
			Vector4<T> operator* (T Number) const;

			//Escala un vector
			friend Vector4<T> operator* (T Number, const Vector4<T>& Other)
			{
			    return Vector4<T>(Other.X*Number, Other.Y*Number, Other.Z*Number, Other.W*Number);
			}

			//Escala un vector
			Vector4<T> &operator*= (T Number);

			//Divide un vector por un numero
			Vector4<T> operator/ (T Number) const;

			//Divide un vector por un numero
			Vector4<T> &operator/= (T Number);

			//Escala un vector por otro vector
			Vector4<T> operator* (const Vector4<T> &Other) const;

			//Escala un vector por otro vector
			Vector4<T> &operator*= (const Vector4<T> &Other);

			//Divide un vector por otro vector
			Vector4<T> operator/ (const Vector4<T> &Other) const;

			//Divide un vector por otro vector
			Vector4<T> &operator/= (const Vector4<T> &Other);

			//Devuelve el vector negativo
			Vector4<T> operator- () const;

			//Operador de igualdad
			bool operator == (const Vector4<T> &Other) const;

			//Operador de desigualdad
			bool operator != (const Vector4<T> &Other) const;

			//Devuelve la magnitud falsa del vector
			T GetPMagnitude() const;

			//Ve si un vector es mayor que otro vector
			bool operator > (const Vector4<T> &Other) const;

			//Ve si un vector es menor que otro vector
			bool operator < (const Vector4<T> &Other) const;

			//Ve si un vector es mayor o igual que otro vector
			bool operator >= (const Vector4<T> &Other) const;

			//Ve si un vector es menor o igual que otro vector
			bool operator <= (const Vector4<T> &Other) const;

			//Devuelve la magnitud del vector elevada al cuadrado
			T GetMagnitudeSq() const;

			//Devuelve la magnitud del vector
			T GetMagnitude() const;
            
			//Normaliza el vector
			Vector4<T>& Normalize();
            
			//Establece la magnitud del vector
			void SetMagnitude(T Magnitude);

			operator T*();
			operator T* () const;

			operator const T* ();
			operator const T* () const;

			//Calcula el producto punto entre dos vectores.
			//Este valor es representativo del coseno entre
			//los dos vectores.
			static T Dot(const Vector4<T> &V1, const Vector4<T> &V2);

			//Calcula el producto cruz entre dos vectores
			static Vector4<T> Cross(const Vector4<T> &V1, const Vector4<T> &V2, const Vector4<T> &V3);

			//Maximiza un vector
			static Vector4<T> Maximize(const Vector4<T>& V1, const Vector4<T>& V2);

			//Minimiza un vector
			static Vector4<T> Minimize(const Vector4<T>& V1, const Vector4<T>& V2);

			//Interpolacion lineal
			static Vector4<T> Lerp(const Vector4<T>& V1, const Vector4<T>& V2, T S);

			//Interpolacion cubica
			static Vector4<T> Hermite(const Vector4<T>& V1, const Vector4<T>& T1, const Vector4<T>& V2, const Vector4<T>& T2, T S);

			//Calcula la proyeccion vectorial del vector B sobre el vector A
			static Vector4<T> Proj(const Vector4<T> &A, const Vector4<T> &B);

			//Calcula el componente escalar del vector B sobre el vector A
			static T Comp(const Vector4<T> &A, const Vector4<T> &B);
		};
		//class Vector4

        template<typename T>
		inline Vector4<T>::Vector4() : 
            X(static_cast<T>(0)), 
            Y(static_cast<T>(0)), 
            Z(static_cast<T>(0)), 
            W(static_cast<T>(0))
		{
		}
		
		
        template<typename T>
		inline Vector4<T>::Vector4(const T *Array)
		{
			X = Array[0];
			Y = Array[1];
			Z = Array[2];
			W = Array[3];
		}


		template<typename T>
		inline Vector4<T>::Vector4(T x, T y, T z, T w) : X(x), Y(y), Z(z), W(w)
		{
		}


		template<typename T>
		inline Vector4<T>::Vector4(const Vector4<T> &v) : X(v.X), Y(v.Y), Z(v.Z), W(v.W)
		{
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::operator+ (const Vector4<T> &Other) const
		{
			return Vector4<T>(X + Other.X, Y + Other.Y, Z + Other.Z, W + Other.W);
		}


		template<typename T>
		inline Vector4<T> &Vector4<T>::operator+= (const Vector4<T> &Other)
		{
			X += Other.X;
			Y += Other.Y;
			Z += Other.Z;
			W += Other.W;

			return *this;
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::operator- (const Vector4<T> &Other) const
		{
			return Vector4<T>(X - Other.X, Y - Other.Y, Z - Other.Z, W - Other.W);
		}


		template<typename T>
		inline Vector4<T> &Vector4<T>::operator-= (const Vector4<T> &Other)
		{
			X -= Other.X;
			Y -= Other.Y;
			Z -= Other.Z;
			W -= Other.W;

			return *this;
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::operator* (T Number) const
		{
			return Vector4<T>(Number * X, Number * Y, Number * Z, Number * W);
		}


		template<typename T>
		inline Vector4<T> &Vector4<T>::operator*= (T Number)
		{
			X *= Number;
			Y *= Number;
			Z *= Number;
			W *= Number;

			return *this;
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::operator/ (T Number) const
		{
			return Vector4<T>(X / Number, Y / Number, Z / Number, W / Number);
		}


		template<typename T>
		inline Vector4<T> &Vector4<T>::operator/= (T Number)
		{
			X /= Number;
			Y /= Number;
			Z /= Number;
			W /= Number;

			return *this;
		}

		template<typename T>
		inline Vector4<T> Vector4<T>::operator* (const Vector4<T> &Other) const
		{
			return Vector4<T>(X * Other.X, Y * Other.Y, Z * Other.Z, W * Other.W);
		}


		template<typename T>
		inline Vector4<T> &Vector4<T>::operator*= (const Vector4<T> &Other)
		{
			X *= Other.X;
			Y *= Other.Y;
			Z *= Other.Z;
			W *= Other.W;

			return *this;
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::operator/ (const Vector4<T> &Other) const
		{
			return Vector4<T>(X / Other.X, Y / Other.Y, Z / Other.Z, W / Other.W);
		}


		template<typename T>
		inline Vector4<T> &Vector4<T>::operator/= (const Vector4<T> &Other)
		{
			X /= Other.X;
			Y /= Other.Y;
			Z /= Other.Z;
			W /= Other.W;

			return *this;
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::operator- () const
		{
			return Vector4<T>(-X, -Y, -Z, -W);
		}


		template<typename T>
		inline bool Vector4<T>::operator == (const Vector4<T> &Other) const
		{
		    if ( abs(Other.X - X) > Epsilon<T>::Value )
                return false;
                
            if ( abs(Other.Y - Y) > Epsilon<T>::Value )
                return false;
                
            if ( abs(Other.Z - Z) > Epsilon<T>::Value )
                return false;
                
            if ( abs(Other.W - W) > Epsilon<T>::Value )
                return false;
                
            return true;
		}


		template<typename T>
		inline bool Vector4<T>::operator != (const Vector4<T> &Other) const
		{
			return ! (*this == Other);
		}


		template<typename T>
		inline T Vector4<T>::GetPMagnitude() const
		{
			return abs(X) + abs(Y) + abs(Z) + abs(W);
		}


		template<typename T>
		inline bool Vector4<T>::operator > (const Vector4<T> &Other) const
		{
			return GetPMagnitude() > Other.GetPMagnitude();
		}


		template<typename T>
		inline bool Vector4<T>::operator < (const Vector4<T> &Other) const
		{
			return GetPMagnitude() < Other.GetPMagnitude();
		}


		template<typename T>
		inline bool Vector4<T>::operator >= (const Vector4<T> &Other) const
		{
			return GetPMagnitude() >= Other.GetPMagnitude();
		}


		template<typename T>
		inline bool Vector4<T>::operator <= (const Vector4<T> &Other) const
		{
			return GetPMagnitude() <= Other.GetPMagnitude();
		}


		template<typename T>
		inline T Vector4<T>::GetMagnitudeSq() const
		{
			return X*X + Y*Y + Z*Z + W*W;
		}


		template<typename T>
		inline T Vector4<T>::GetMagnitude() const
		{
			return ::sqrt(GetMagnitudeSq());
		}


		template<typename T>
		inline Vector4<T>& Vector4<T>::Normalize()
		{
            T InvMagnitude = static_cast<T>(1) / GetMagnitude();
		    
		    X *= InvMagnitude;
		    Y *= InvMagnitude;
		    Z *= InvMagnitude;
		    W *= InvMagnitude;
		    
		    return *this;
		}


		template<typename T>
		inline void Vector4<T>::SetMagnitude(T Magnitude)
		{
			(*this) *= (Magnitude/GetMagnitude());
		}


		template<typename T>
		inline Vector4<T>::operator T*()
		{
			return &X;
		}
		
		
		template<typename T>
		inline Vector4<T>::operator T*() const
		{
			return &X;
		}


        template<typename T>
		inline Vector4<T>::operator const T* ()
		{
			return &X;
		}


		template<typename T>
		inline Vector4<T>::operator const T* () const
		{
			return &X;
		}


		template<typename T>
		inline T Vector4<T>::Dot(const Vector4<T> &V1, const Vector4<T> &V2)
		{
			return V1.X*V2.X + V1.Y*V2.Y + V1.Z*V2.Z + V1.W*V2.W;
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::Cross(const Vector4<T> &v1, const Vector4<T> &v2, const Vector4<T> &v3)
		{
			return Vector4<T>
			(
				v1.Y*(v2.Z*v3.W - v3.Z*v2.W) - v1.Z*(v2.Y*v3.W - v3.Y*v2.W) + v1.W*(v2.Y*v3.Z - v3.Y*v2.Z),
				-(v1.X*(v2.Z*v3.W - v3.Z*v2.W) - v1.Z*(v2.X*v3.W - v3.X*v2.W) + v1.W*(v2.X*v3.Z - v3.X*v2.Z)),
				v1.X*(v2.Y*v3.W - v3.Y*v2.W) - v1.Y*(v2.X*v3.W - v3.X*v2.W) + v1.W*(v2.X*v3.Y - v3.X*v2.Y),
				-(v1.X*(v2.Y*v3.Z - v3.Y*v2.Z) - v1.Y*(v2.X*v3.Z - v3.X*v2.Z) + v1.Z*(v2.X*v3.Y - v3.X*v2.Y))
			);
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::Maximize(const Vector4<T>& V1, const Vector4<T>& V2)
		{
			return Vector4<T>( std::max<T>(V1.X, V2.X), std::max<T>(V1.Y, V2.Y), std::max<T>(V1.Z, V2.Z), std::max<T>(V1.W, V2.W) );
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::Minimize(const Vector4<T>& V1, const Vector4<T>& V2)
		{
			return Vector4<T>( std::min<T>(V1.X, V2.X), std::min<T>(V1.Y, V2.Y), std::min<T>(V1.Z, V2.Z), std::min<T>(V1.W, V2.W) );
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::Lerp(const Vector4<T>& V1, const Vector4<T>& V2, T S)
		{
		    return S*V2 + V1*( static_cast<T>(1) - S);
		}


		template<typename T>
		inline Vector4<T> Vector4<T>::Hermite(const Vector4<T>& V1, const Vector4<T>& T1, const Vector4<T>& V2, const Vector4<T>& T2, T s)
		{
            T ss, sss, _2sss, _3ss;

			ss = s*s;
			sss = ss*s;

			_2sss = sss+sss;
			_3ss = ss+ss+ss;
			
			return	V1*(_2sss - _3ss + static_cast<T>(1))+ 
					V2*(_3ss - _2sss)		+ 
					T1*(sss - (ss+ss) + s)	+ 
					T2*(sss - ss)			;
		}

		template<typename T>
		inline Vector4<T> Vector4<T>::Proj(const Vector4<T> &A, const Vector4<T> &B)
		{
			return ( Vector4<T>::Dot(A, B)/A.GetMagnitudeSq() )  *  A;
		}


		template<typename T>
		inline T Vector4<T>::Comp(const Vector4<T> &A, const Vector4<T> &B)
		{
			return Vector4<T>::Dot(A, B)/A.GetMagnitude();
		}
    }
}

#endif  //__REVENG_VECTOR4_HPP__
